package cli

import (
	"flag"
	"reflect"
	"fmt"
	"strconv"

	"gitee.com/wint/tge/session"
)

type CmdWriter struct {
	ses 	*session.Session
}

func (w *CmdWriter) Write(buf []byte) (int, error) {
	if nil != w.ses {
		return w.ses.Send(string(buf))
	} else {
		fmt.Println(string(buf))
	}
	return len(buf), nil
}

type CmdParam struct {
	name 	string
	val 	interface{}
	dec 	string
	isFlag	bool 	// 是否标签参数
	isNeed	bool
}

// 命令定义
type Cmd struct {
	Name	string 	// 命令名称
	Usage 	string 	// 命令使用说明

	handler	interface{}	// 响应函数，使用反射机制来调用，参数列表按命令的添加顺序来使用，可以把用户会话当会参数传入

	params 	[]*CmdParam
	isChecked 	bool 	// 是否已经检查过了
	isEnable	bool 	// 是否有效的命令
}

func NewCmd(name string, usage string) *Cmd {
	c := &Cmd{}
	c.Name = name
	c.Usage = usage

	mgr.AddCmd(c)

	return c
}

// 添加命令的参数，这些参数会按照添加的顺序传递给响应函数
// name: 该参数的名字
// defaultVal: 默认值
// dec: 命令的描述
// isNeed: 是否可以省略，只对无标签参数有效
// isFlag: 是否标签参数
// 只支持字符串、数字、布尔三种类型的参数
func (c *Cmd) AddParam(name string, defaultVal interface{}, dec string, isFlag bool, isNeed bool) {
	c.isChecked = false
	c.params = append(c.params, &CmdParam{name, defaultVal, dec, isFlag, isNeed})
}

// 添加无标签参数，isNeed: 是否可以省略，可选参数必须在后面，不然无法识别哪个参数是被省略的
func (c *Cmd) AddArgParam(name string, defaultVal interface{}, dec string, isNeed bool) {
	c.AddParam(name, defaultVal, dec, false, isNeed)
}

// 添加带标签参数
func (c *Cmd) AddFlagParam(name string, defaultVal interface{}, dec string) {
	c.AddParam(name, defaultVal, dec, true, false)
}

// 设置响应函数
func (c *Cmd) SetHandler(h interface{}) {
	c.isChecked = false
	if reflect.TypeOf(h).Kind() == reflect.Func {
		c.handler = h
	} else {
		fmt.Println("Cmd handler must be a callable func")
	}
}

// 执行命令
func (c *Cmd) Execute(params []string, ses *session.Session) {
	if nil != c.handler {
		t := reflect.TypeOf(c.handler)
		if !c.IsValid(ses) {
			return
		}
		var in = make([]reflect.Value, t.NumIn())
		if t.NumIn() > 0 {
			s := flag.NewFlagSet(c.Name, flag.ContinueOnError)
			if nil != ses {
				s.SetOutput(&CmdWriter{ses})
			}
			values := make([]interface{}, 0, t.NumIn())
			ptrMap := make(map[int]int) 	// 存储 flag 位置
			argvs := make([]int, 0) 	// 非 flag 参数的在位置
			for k, v := range c.params {
				if !v.isFlag {
					values = append(values, v.val)
					argvs = append(argvs, k)
				} else {
					switch v.val.(type) {
					case int:
						val := s.Int(v.name, v.val.(int), v.dec)
						values = append(values, val)
					case string:
						val := s.String(v.name, v.val.(string), v.dec)
						values = append(values, val)
					case bool:
						val := s.Bool(v.name, v.val.(bool), v.dec)
						values = append(values, val)
					}
					ptrMap[k] = k
				}
			}

			if nil != s.Parse(params) {
				return
			}

			if len(argvs) > 0 && s.NArg() > 0 {
				for k, v := range s.Args() {
					if k < len(argvs) {
						i := argvs[k]
						switch values[i].(type) {
						case int:
							if iv, err := strconv.Atoi(v); nil == err {
								values[i] = iv
							} else {
								msg := fmt.Sprintf("[%s] flag param not found for type int at index %d", c.Name, i)
								c.Println(ses, msg)
								c.PrintUsage(ses)
								return
							}
						case string:
							values[i] = v
						case bool:
							if iv, err := strconv.ParseBool(v); nil == err {
								values[i] = iv
							} else {
								msg := fmt.Sprintf("[%s] flag param not found for type bool at index %d\n", c.Name, i)
								c.Println(ses, msg)
								c.PrintUsage(ses)
								return
							}
						}
					} else {
						break
					}
				}
			}
			add := 0
			for j := 0; j < t.NumIn(); j++ {
				argType := t.In(j)
				if argType == reflect.TypeOf(ses) {
					in[j] = reflect.ValueOf(ses)
					add++
					continue
				} else if argType == reflect.TypeOf(c) {
					in[j] = reflect.ValueOf(c)
					add++
					continue
				}
				i := j - add
				var v interface{}
				if _, ok := ptrMap[i]; ok {
					switch values[i].(type) {
					case *int:
						v = *(values[i].(*int))
					case *string:
						v = *(values[i].(*string))
					case *bool:
						v = *(values[i].(*bool))
					default:
						msg := fmt.Sprintf("param error type %v\n", reflect.TypeOf(values[i]))
						c.Println(ses, msg)
						c.PrintUsage(ses)
						return
					}
				} else {
					v = values[i]
				}
				if argType != reflect.TypeOf(v) {
					msg := fmt.Sprintf("[%s] args not found for type %v at index %d, get type %v\n", c.Name, argType, i, reflect.TypeOf(v))
					c.Println(ses, msg)
					return
				}

				in[j] = reflect.ValueOf(v)
			}
		}

		reflect.ValueOf(c.handler).Call(in)
	}
}

// 执行 json 格式命令
func (c *Cmd) ExecuteJson(params map[string]interface{}, ses *session.Session) {
	if nil != c.handler {
		t := reflect.TypeOf(c.handler)
		if !c.IsValid(ses) {
			return
		}

		var in = make([]reflect.Value, t.NumIn())
		if t.NumIn() > 0 {
			values := make([]interface{}, 0, t.NumIn())

			for _, v := range c.params {
				if i, ok := params[v.name]; ok {
					switch v.val.(type) {
					case int:
						if iv, ok := i.(int); ok {
							values = append(values, iv)
						} else if iv, ok := i.(float64); ok {
							values = append(values, int(iv))
						} else {
							msg := fmt.Sprintf("[%s] param not found for type int with name %s", c.Name, v.name)
							c.Println(ses, msg)
							c.PrintUsage(ses)
							return
						}
					case string:
						if iv, ok := i.(string); ok {
							values = append(values, iv)
						} else {
							msg := fmt.Sprintf("[%s] param not found for type string with name %s", c.Name, v.name)
							c.Println(ses, msg)
							c.PrintUsage(ses)
							return
						}
					case bool:
						if iv, ok := i.(bool); ok {
							values = append(values, iv)
						} else {
							msg := fmt.Sprintf("[%s] param not found for type bool with name %s", c.Name, v.name)
							c.Println(ses, msg)
							c.PrintUsage(ses)
							return
						}
					}
				} else {
					values = append(values, v.val)
				}
			}

			add := 0
			for j := 0; j < t.NumIn(); j++ {
				argType := t.In(j)
				if argType == reflect.TypeOf(ses) {
					in[j] = reflect.ValueOf(ses)
					add++
					continue
				} else if argType == reflect.TypeOf(c) {
					in[j] = reflect.ValueOf(c)
					add++
					continue
				}
				i := j - add
				var v interface{}
				switch values[i].(type) {
				case int:
					v = values[i]
				case string:
					v = values[i]
				case bool:
					v = values[i]
				default:
					msg := fmt.Sprintf("param error type %v\n", reflect.TypeOf(values[i]))
					c.Println(ses, msg)
					c.PrintUsage(ses)
					return
				}
				if argType != reflect.TypeOf(v) {
					msg := fmt.Sprintf("[%s] args not found for type %v at index %d, get type %v\n", c.Name, argType, i, reflect.TypeOf(v))
					c.Println(ses, msg)
					return
				}

				in[j] = reflect.ValueOf(v)
			}
		}

		reflect.ValueOf(c.handler).Call(in)
	}
}

func (c *Cmd) PrintUsage(ses *session.Session) {
	msg := c.Usage + "\n"
	msg += c.Name
	for _, v := range c.params {
		if !v.isFlag {
			if v.isNeed {
				msg += " " + v.name
			} else {
				msg += " [" + v.name + "]"
			}
		}
	}
	msg += "\n"
	for _, v := range c.params {
		if v.isFlag {
			msg += "    -" + v.name + "\t" + v.dec + "\n"
		}
	}
	for _, v := range c.params {
		if !v.isFlag {
			msg += "    " + v.name + "\t" + v.dec + "\n"
		}
	}
	c.Println(ses, msg)
}

func (c *Cmd) PrintSimpleUsage(ses *session.Session) {
	msg := c.Name + "\t" + c.Usage + "\t"
	for _, v := range c.params {
		if !v.isFlag {
			if v.isNeed {
				msg += " " + v.name
			} else {
				msg += " [" + v.name + "]"
			}
		}
	}
	c.Println(ses, msg)
}

func (c *Cmd) Println(ses *session.Session, msg string) {
	if nil != ses {
		ses.Send(msg)
	} else {
		fmt.Println(msg)
	}
}

// 检查命令参数是否匹配
func (c *Cmd) IsValid(ses *session.Session) bool {
	if nil != c.handler {
		if c.isChecked {
			return c.isEnable
		}
		c.isEnable = false
		t := reflect.TypeOf(c.handler)
		n := t.NumIn()
		for i := 0; i < t.NumIn(); i++ {
			if t.In(i) == reflect.TypeOf(ses) {
				n--
			} else if t.In(i) == reflect.TypeOf(c) {
				n--
			}
		}
		if len(c.params) == n {
			c.isEnable = true
		}

		if t.NumIn() > 0 && c.isEnable {
			add := 0
			for i := 0; i < t.NumIn(); i++ {
				argType := t.In(i)
				if argType == reflect.TypeOf(ses) {
					add++
					continue
				} else if argType == reflect.TypeOf(c) {
					add++
					continue
				}
				if argType != reflect.TypeOf(c.params[i - add].val) {
					c.isEnable = false
					break
				}
			}
		}
		if !c.isEnable {
			c.Println(ses, "wrong param with cmd " + c.Name)
			need := "    handler ("
			for i := 0; i < t.NumIn(); i++ {
				need += t.In(i).String()
				if i != t.NumIn() - 1 {
					need += ", "
				}
				
			}
			need += ")\n"
				
			need += "    params ("
			for k, v := range c.params {
				need += reflect.TypeOf(v.val).String()
				if k != len(c.params) - 1 {
					need += ", "
				}
			}
			need += ")\n"
			c.Println(ses, need)
			c.PrintUsage(ses)
		} else {
			c.isChecked = true
		}
		return c.isEnable
	}
	return false
}